import threading
import time
from abc import ABC, abstractmethod

import pymysql

import facade
from .threadpoolmanger import ThreadPoolManger
from xjlibrary.ringList.RingList import RingList


class MThreadingRun(ABC):
    def __init__(self, num, logger=None, is_static_max=True):
        if logger:
            self.logger = logger
        else:
            self.logger = facade.get_streamlogger()
        # 线程数
        self.threadingnum = num
        # 代理列表
        self.list_proxy = RingList()
        # 线程池
        self.thread_pool = ThreadPoolManger(self.threadingnum, self.logger, is_static_max=is_static_max)
        self.thread_pool.set_callback(self.thread_pool_hook)
        # 结果集
        self.results = []
        # 线程方法
        self.func = self.fun
        # 结果数
        self.resultnum = 0
        # 工作线程数
        self.jobnum = 0
        # 结果集被处理标志  默认被处理 是为了兼容之前的代码不去改动
        self.dealresultstatus = True
        # 结果到达该数量后处理结果 默认及时处理
        self.dealresultsnum = 0
        # 代理设置时间
        self.proxytime = 0
        self.modle = 1
        # 在任务和处理结果时event信号的状态
        self.result_event_status = True
        self.task_event_status = True
        self.thread_run_lock = threading.Lock()

    @abstractmethod
    def fun(self, threadval, *args, **kwargs):
        pass

    @abstractmethod
    def thread_pool_hook(self, thread_pool_dicts: dict, dictsfull: dict, *args, **kwargs):
        """
        钩子函数 可以被重写
        主要重写里面的dicts部分
        :return:
        """
        pass

    @abstractmethod
    def setTask(self, results=None, *args, **kwargs):
        # self.thread_pool.event.set()  # 自动释放信号
        # 当设置任务和处理结果使用同一个连接时尽量使用event信号保证不同时执行sql在一个执行未返回时
        # 请查询较大数据时主动释放
        pass

    @abstractmethod
    def getTask(self, *args, **kwargs):
        """
        主要用于mysql的请求
        :param args:
        :param kwargs:
        :return:
        """
        pass

    def set_mysqlpool(self,creator=pymysql,  # 使用链接数据库的模块
                      maxconnections=5,  # 连接池允许的最大连接数，0和None表示没有限制
                      mincached=0,  # 初始化时，连接池至少创建的空闲的连接，0表示不创建
                      maxcached=0,  # 连接池空闲的最多连接数，0和None表示没有限制
                      maxshared=0,
                      # 连接池中最多共享的连接数量，0和None表示全部共享，ps:其实并没有什么用，因为pymsql和MySQLDB等模块中的threadsafety都为1，所有值无论设置多少，_maxcahed永远为0，所以永远是所有链接共享
                      maxusage=None,  # 一个链接最多被使用的次数，None表示无限制
                      blocking=True,  # 链接池中如果没有可用共享连接后，是否阻塞等待，True表示等待，False表示不等待然后报错
                      setsession=[],  # 开始会话前执行的命令列表
                      ping=1,  # ping Mysql 服务端，检查服务是否可用
                      host='127.0.0.1',
                      port=3306,
                      user='root',
                      password='123',
                      database='ok1',
                      charset='utf8'):
        self.thread_pool.set_mysqlpool(creator, maxconnections, mincached, maxcached, maxshared, maxusage, blocking,
                                       setsession,
                                       ping, host, port, user, password, database, charset)

    # 上层重写的方法必须调用该方法
    def setProxy(self, proxysList=None):
        """
        将代理加入到循环队列中
        :param proxysList:
        :return:
        """
        if proxysList:
            self.proxytime = int(time.time())
            for proxy in proxysList:
                if proxy:
                    # 判断代理是否在环型列表中
                    if not self.list_proxy.exist(proxy):
                        # 没在则加入
                        self.list_proxy.append(proxy)

    def __setTask(self, *args, **kwargs):
        # 设置等待任务队列为空时继续添加任务
        # 继承类必须在第一步调用父类的该方法
        while True:
            # 获取Event 状态
            self.task_event_status = True
            # self.logger.info("self.thread_pool.event_is_set() is {}".format(self.thread_pool.event_is_set()))
            # 如果状态为False 即　堵塞状态
            if not self.thread_pool.event_is_set():
                # 判断　result_event_status　和　task_event_status　都堵塞　理论上不存在　但Event并不完美
                if self.result_event_status and self.task_event_status:
                    # 设置　信号允许堵塞通过　状态为True
                    self.thread_pool.event_set()
                time.sleep(1)
                continue
            self.logger.info("event_wait")
            # 等待信号后执行设置任务
            self.thread_pool.event_wait(50)
            # 得到信号后clrar 不允许其他地方运行 状态　False
            self.thread_pool.event_clear()
            if self.result_event_status is False:
                self.logger.info("elf.result_event_status is False")
                continue
            self.task_event_status = False
            # 执行getTask
            self.logger.info("getTask")
            result = self.getTask(*args, **kwargs)
            # 运行完毕 其他地方等待的信号可以继续运行 True
            self.thread_pool.event_set()
            self.task_event_status = True
            # 执行ste()
            reslt = self.setTask(result, *args, **kwargs)

            # 是否跳出循环　结束线程
            if reslt == "break":
                self.logger.info("线程{}结束".format(threading.currentThread().getName()))
                break
            time.sleep(1)

    def deal_results(self, *args, **kwargs):
        """
        现在单开线程处理结果
        :return:
        """
        self.logger.info("开始处理结果")
        while True:
            self.result_event_status = True
            # self.logger.info("self.thread_pool.event_is_set() is {}".format(self.thread_pool.event_is_set()))
            # 处理结果标识
            self.dealresultstatus = True
            # 从结果队列获取结果到results
            self.getreustlFromQueue()
            if len(self.results) > self.dealresultsnum or self.thread_pool.work_queue_is_empty() \
                    or not self.thread_pool.thread_queue.empty():
                if len(self.results) > 0 or not self.thread_pool.thread_queue.empty():
                    # 处理结果
                    # 为防止同一个连接多线程操作出现问题使用信号加锁
                    # 等待event为true
                    if not self.thread_pool.event_is_set():
                        if self.result_event_status and self.task_event_status:
                            self.thread_pool.event_set()
                        time.sleep(1)
                        continue
                    self.thread_pool.event_wait(60)
                    # 设置event为false不允许其他等待线程操作
                    self.thread_pool.event_clear()
                    if self.task_event_status is False:
                        continue
                    self.result_event_status = False
                    # 处理结果
                    self.dealresult()
                    # 本地操作完毕 允许其他线程操作mysql
                    self.thread_pool.event_set()
                    self.result_event_status = True
                    if self.dealresultstatus:
                        # 处理完结果后要清理
                        self.results.clear()

    def __setProxy(self, *args, **kwargs):
        """
        设置代理的循环
        :param args:
        :param kwargs:
        :return:
        """
        while True:
            result = self.setProxy(*args, **kwargs)
            if result == "break":
                self.logger.info("线程{}结束".format(threading.currentThread().getName()))
                break

    def start_especial_thread(self):
        # 开启一个线程设置任务
        self.thread_pool.set_add_especial_thread(self.__setTask, "task_thread_1")
        self.thread_pool.set_add_especial_thread(self.__setProxy, "proxy_thread_1")
        self.thread_pool.set_add_especial_thread(self.deal_results, "deal_result_1")
        self.thread_pool.start(threadtype="especial")

    def setTask_noevent(self, *args, **kwargs):
        while True:
            results = self.getTask(*args, **kwargs)
            reslt = self.setTask(results, *args, **kwargs)
            if reslt == "break":
                self.logger.info("线程{}结束".format(threading.currentThread().getName()))
                break

    def deal_results_no_event(self, *args, **kwargs):
        """
        该函数用于没有信号的机制里
        使用信号主要是因为多线程无法同时使用一个链接
        有可能出现错误
        :param args:
        :param kwargs:
        :return:
        """
        while True:
            # 处理结果标识
            self.dealresultstatus = True
            # 从结果队列获取结果到results
            self.getreustlFromQueue()
            if len(
                    self.results) > self.dealresultsnum or self.thread_pool.work_queue.empty() or not self.thread_pool.thread_queue:
                if len(self.results) > 0 or not self.thread_pool.thread_queue.empty():
                    # 处理结果
                    self.dealresult()
                    if self.dealresultstatus:
                        # 处理完结果后要清理
                        self.results.clear()

    def start_especial_thread_no_evnt(self):
        # 开启一个线程设置任务
        self.thread_pool.set_add_especial_thread(self.setTask_noevent, "task_thread_1")
        self.thread_pool.set_add_especial_thread(self.__setProxy, "proxy_thread_1")
        self.thread_pool.set_add_especial_thread(self.deal_results_no_event, "deal_result_1")
        self.thread_pool.start(threadtype="especial")

    def dealresult(self, *args, **kwargs):
        """
        如果没有在这里处理 请将 self.dealresultstatus = False这样不会丢数据
        :param args:
        :param kwargs:
        :return:
        """
        pass

    def setfunc(self, func):
        # 设置线程方法
        self.func = func

    def add_job(self, func, *args, **kwargs):
        self.jobnum += 1
        self.thread_pool.add_job(func, *args, **kwargs)

    def getreustlFromQueue(self):
        # 从结果队列获取结果到results
        once_result_num = 0
        while not self.thread_pool.result_queue_is_empty():
            self.resultnum += 1
            once_result_num += 1
            if once_result_num > 100:
                return
            result = self.thread_pool.result_queue.get()
            self.results.append(result)
            self.thread_pool.result_queue.task_done()

    def checkResultsfininsh(self, *args, **kwargs):
        """
        该函数用于处理运行到最后时结果不足100的情况
        :return:
        """
        if self.thread_pool.work_queue_is_empty():
            t1 = len(self.results)
            self.logger.info("self.results 里还有结果 %s 条 " % str(t1))
            if t1 != 0:
                # self.dealresult(*args, **kwargs)
                # self.results.clear()
                return False
            else:
                return True

    def other(self):
        # self.logger.info("工作队列任务量为{},结果队列任务量为{}".format(self.thread_pool.get_workqueue_size(),
        #                                                 self.thread_pool.get_resultqueue_size()))
        # 通过工作队列和结果队列观察是否结束
        if self.thread_pool.checkThreadRunFinish():
            self.logger.info("初次判断任务已经结束，各个队列为空")
            return True
        else:
            return False

    def check_especial_thread(self):
        if self.modle == 1:
            task = self.__setTask
            proxy = self.__setProxy
            result = self.deal_results
        elif self.modle == 2:
            task = self.setTask_noevent
            proxy = self.__setProxy
            result = self.deal_results_no_event
        else:
            raise Exception("不存在指定model")
        nowThreadsName = []  # 用来保存当前线程名称
        now = threading.enumerate()  # 获取当前线程名
        for i in now:
            nowThreadsName.append(i.getName())  # 保存当前线程名称

        for name in list(self.thread_pool.thread_pool_dicts.keys()):
            thread = self.thread_pool.thread_pool_dicts[name]["thread"]
            if name in ["task_thread_1", "proxy_thread_1", "deal_result_1"]:
                if name in nowThreadsName and thread.is_alive():
                    continue
                if not thread.is_restart:
                    continue
                if name == "task_thread_1":
                    taskin = task
                elif name == "proxy_thread_1":
                    taskin = proxy
                elif name == "deal_result_1":
                    taskin = result
                else:
                    raise Exception("没有对应的任务，请检查")
                is_start = False
                if name in self.thread_pool.thread_pool_dicts:
                    if self.thread_pool.thread_pool_dicts[name]["thread"].is_alive():
                        is_start = True
                if not is_start:
                    args = self.thread_pool.thread_pool_dicts[name]["args"]
                    kwargs = self.thread_pool.thread_pool_dicts[name]["kwargs"]
                    self.thread_pool.set_add_especial_thread(taskin, name, *args, **kwargs)

    def is_break(self):
        return False

    def run(self, model=1):
        self.logger.info("开始多线程")
        self.modle = model
        if model == 1:
            self.logger.info("有event模式,主要用于mysql多线程不安全的问题")
            # 有event
            self.start_especial_thread()
        elif model == 2:
            self.logger.info("无event模式")
            self.start_especial_thread_no_evnt()
        while True:
            time.sleep(3)
            self.thread_pool.checkThread()
            self.check_especial_thread()
            if self.other():
                if not self.checkResultsfininsh():
                    continue
                else:
                    self.logger.info("进入other 判断 再次确认finish")
                    if self.thread_pool.work_queue.empty() and self.thread_pool.result_queue.empty() \
                            and len(self.results) == 0:
                        self.logger.info("运行完毕")
                        if self.is_break():
                            self.logger.info("10 s 后退出多线程")
                            time.sleep(10)
                            break
